Débloquez une validation de formulaire moderne et puissante dans React. Ce guide complet explore le hook experimental_useFormStatus, les actions serveur et le paradigme de validation d'état pour créer des formulaires robustes et performants.
Maîtriser la validation de formulaire avec la fonction `experimental_useFormStatus` de React
Les formulaires sont la pierre angulaire de l'interaction web. D'une simple inscription à une newsletter à une application financière complexe en plusieurs étapes, ils sont le principal canal par lequel les utilisateurs communiquent avec nos applications. Pourtant, pendant des années, la gestion de l'état des formulaires dans React a été une source de complexité, de code répétitif et de lassitude face aux dépendances. Nous avons jonglé avec des composants contrôlés, lutté avec des bibliothèques de gestion d'état et écrit d'innombrables gestionnaires `onChange`, le tout dans la poursuite d'une expérience utilisateur transparente et intuitive.
L'équipe React a repensé cet aspect fondamental du développement web, ce qui a conduit à l'introduction d'un nouveau paradigme puissant centré sur les Actions serveur React. Ce nouveau modèle, construit sur les principes de l'amélioration progressive, vise à simplifier la gestion des formulaires en rapprochant la logique de l'endroit où elle appartient, souvent le serveur. Au cœur de cette révolution côté client se trouvent deux nouveaux hooks expérimentaux : `useFormState` et la star de notre discussion d'aujourd'hui, `experimental_useFormStatus`.
Ce guide complet vous emmènera dans une plongée en profondeur dans le hook `experimental_useFormStatus`. Nous n'examinerons pas seulement sa syntaxe, nous explorerons le modèle mental qu'il permet : La logique de validation basée sur l'état. Vous apprendrez comment ce hook découple l'interface utilisateur de l'état du formulaire, simplifie la gestion des états en attente et fonctionne de concert avec les actions serveur pour créer des formulaires robustes, accessibles et hautement performants qui fonctionnent même avant le chargement de JavaScript. Préparez-vous à repenser tout ce que vous pensiez savoir sur la création de formulaires dans React.
Un changement de paradigme : L'évolution des formulaires React
Pour pleinement apprécier l'innovation qu'apporte `useFormStatus`, nous devons d'abord comprendre le parcours de la gestion des formulaires dans l'écosystème React. Ce contexte met en évidence les problèmes que cette nouvelle approche résout élégamment.
L'ancienne garde : Composants contrôlés et bibliothèques tierces
Pendant des années, l'approche standard des formulaires dans React a été le modèle de composant contrôlé. Cela implique :
- L'utilisation d'une variable d'état React (par exemple, de `useState`) pour contenir la valeur de chaque champ de formulaire.
- L'écriture d'un gestionnaire `onChange` pour mettre à jour l'état à chaque frappe.
- Le renvoi de la variable d'état à la propriété `value` de l'entrée.
Bien que cela donne à React un contrôle total sur l'état du formulaire, cela introduit un code répétitif important. Pour un formulaire avec dix champs, vous pourriez avoir besoin de dix variables d'état et de dix fonctions de gestionnaire. La gestion de la validation, des états d'erreur et de l'état de soumission ajoute encore plus de complexité, ce qui amène souvent les développeurs à créer des hooks personnalisés complexes ou à se tourner vers des bibliothèques tierces complètes.
Des bibliothèques comme Formik et React Hook Form ont acquis une notoriété en abstrayant cette complexité. Elles fournissent des solutions brillantes pour la gestion de l'état, la validation et l'optimisation des performances. Cependant, elles représentent une autre dépendance à gérer et fonctionnent souvent entièrement côté client, ce qui peut entraîner une duplication de la logique de validation entre le frontend et le backend.
La nouvelle ère : Amélioration progressive et actions serveur
Les actions serveur React introduisent un changement de paradigme. L'idée centrale est de s'appuyer sur les fondations de la plateforme web : l'élément HTML `
Un exemple simple : Le bouton de soumission intelligent
Voyons le cas d'utilisation le plus courant en action. Au lieu d'un `<button>` standard, nous allons créer un composant qui est conscient de l'état de soumission du formulaire.
Fichier : SubmitButton.js
import { experimental_useFormStatus as useFormStatus } from 'react-dom';
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Soumission...' : 'S'inscrire'}
</button>
);
}
Fichier : SignUpForm.js
import { SubmitButton } from './SubmitButton';
import { signUpAction } from './actions'; // Une action serveur
export function SignUpForm() {
return (
<form action={signUpAction}>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" required />
<br />
<SubmitButton />
</form>
);
}
Dans cet exemple, le `SubmitButton` est complètement autonome. Il ne reçoit aucune prop. Il utilise `useFormStatus` pour savoir quand le `SignUpForm` est en attente et se désactive automatiquement et modifie son texte. Il s'agit d'un modèle puissant pour découpler et créer des composants réutilisables et conscients des formulaires.
Au cœur du problème : La logique de validation basée sur l'état
Nous arrivons maintenant au concept central. `useFormStatus` ne sert pas seulement aux états de chargement ; c'est un élément clé qui permet d'envisager la validation d'une manière différente.
Définir la "validation d'état"
La Validation basée sur l'état est un modèle où les commentaires de validation sont principalement fournis à l'utilisateur en réponse à une tentative de soumission de formulaire. Au lieu de valider à chaque frappe (`onChange`) ou lorsqu'un utilisateur quitte un champ (`onBlur`), la logique de validation principale s'exécute lorsque l'utilisateur soumet le formulaire. Le résultat de cette soumission, son *état* (par exemple, succès, erreur de validation, erreur serveur), est ensuite utilisé pour mettre à jour l'interface utilisateur.
Cette approche s'aligne parfaitement avec les actions serveur React. L'action serveur devient la source unique de vérité pour la validation. Elle reçoit les données du formulaire, les valide par rapport à vos règles métier (par exemple, "cet e-mail est-il déjà utilisé ?") et renvoie un objet d'état structuré indiquant le résultat.
Le rôle de son partenaire : `experimental_useFormState`
`useFormStatus` nous indique *ce qui* se passe (en attente), mais il ne nous dit pas le *résultat* de ce qui s'est passé. Pour cela, nous avons besoin de son hook frère : `experimental_useFormState`.
`useFormState` est un hook conçu pour mettre à jour l'état en fonction du résultat d'une action de formulaire. Il prend la fonction d'action et un état initial comme arguments et renvoie un nouvel état et une fonction d'action encapsulée à transmettre à votre formulaire.
const [state, formAction] = useFormState(myAction, initialState);
- `state` : Cela contiendra la valeur de retour de la dernière exécution de `myAction`. C'est là que nous obtiendrons nos messages d'erreur.
- `formAction` : Il s'agit d'une nouvelle version de votre action que vous devez transmettre à la propriété `action` du `
`. Lorsque celle-ci est appelée, elle déclenchera l'action d'origine et mettra à jour l'`état`.
Le flux de travail combiné : Du clic au feedback
Voici comment `useFormState` et `useFormStatus` fonctionnent ensemble pour créer une boucle de validation complète :
- Rendu initial : Le formulaire est rendu avec un état initial fourni par `useFormState`. Aucune erreur n'est affichée.
- Soumission de l'utilisateur : L'utilisateur clique sur le bouton de soumission.
- État en attente : Le hook `useFormStatus` dans le bouton de soumission signale immédiatement `pending : true`. Le bouton devient désactivé et affiche un message de chargement.
- Exécution de l'action : L'action serveur (encapsulée par `useFormState`) est exécutée avec les données du formulaire. Elle effectue la validation.
- Retour de l'action : L'action échoue à la validation et renvoie un objet d'état, par exemple : <br />`{ message: "Validation échouée", erreurs: { email: "Cet e-mail est déjà utilisé." } }`
- Mise à jour de l'état : `useFormState` reçoit cette valeur de retour et met à jour sa variable `state`. Cela déclenche un nouveau rendu du composant formulaire.
- Feedback de l'interface utilisateur : Le formulaire est rendu à nouveau. L'état `pending` de `useFormStatus` devient `false`. Le composant peut maintenant lire `state.errors.email` et afficher le message d'erreur à côté du champ de saisie de l'e-mail.
L'ensemble de ce flux fournit un feedback clair et faisant autorité au serveur à l'utilisateur, entièrement piloté par l'état de soumission et le résultat.
Masterclass pratique : Création d'un formulaire d'inscription multi-champs
Consolidons ces concepts en construisant un formulaire d'inscription complet, de style production. Nous utiliserons une action serveur pour la validation et `useFormState` et `useFormStatus` pour créer une excellente expérience utilisateur.
Étape 1 : Définition de l'action serveur avec validation
Tout d'abord, nous avons besoin de notre action serveur. Pour une validation robuste, nous utiliserons la bibliothèque populaire Zod. Cette action se trouvera dans un fichier séparé, marqué avec la directive `'use server';` si vous utilisez un framework comme Next.js.
Fichier : actions/authActions.js
'use server';
import { z } from 'zod';
// Définir le schéma de validation
const registerSchema = z.object({
username: z.string().min(3, 'Le nom d'utilisateur doit comporter au moins 3 caractères.'),
email: z.string().email('Veuillez saisir une adresse e-mail valide.'),
password: z.string().min(8, 'Le mot de passe doit comporter au moins 8 caractères.'),
});
// Définir l'état initial de notre formulaire
export const initialState = {
message: '',
errors: {},
};
export async function registerUser(prevState, formData) {
// 1. Valider les données du formulaire
const validatedFields = registerSchema.safeParse(
Object.fromEntries(formData.entries())
);
// 2. Si la validation échoue, renvoyer les erreurs
if (!validatedFields.success) {
return {
message: 'La validation a échoué. Veuillez vérifier les champs.',
errors: validatedFields.error.flatten().fieldErrors,
};
}
// 3. (Simuler) Vérifier si l'utilisateur existe déjà dans la base de données
// Dans une application réelle, vous interrogeriez votre base de données ici.
if (validatedFields.data.email === 'user@example.com') {
return {
message: 'L'inscription a échoué.',
errors: { email: ['Cet e-mail est déjà enregistré.'] },
};
}
// 4. (Simuler) Créer l'utilisateur
console.log('Création de l'utilisateur :', validatedFields.data);
// 5. Renvoyer un état de succès
// Dans une application réelle, vous pourriez rediriger ici en utilisant `redirect()` de 'next/navigation'
return {
message: 'Utilisateur enregistré avec succès !',
errors: {},
};
}
Cette action serveur est le cerveau de notre formulaire. Elle est autonome, sécurisée et fournit une structure de données claire pour les états de succès et d'erreur.
Étape 2 : Création de composants réutilisables et conscients de l'état
Pour garder notre composant formulaire principal propre, nous allons créer des composants dédiés pour nos entrées et notre bouton de soumission.
Fichier : components/SubmitButton.js
'use client';
import { experimental_useFormStatus as useFormStatus } from 'react-dom';
export function SubmitButton({ label }) {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
aria-disabled={pending}
>
{pending ? 'Traitement...' : label}
</button>
);
}
Remarquez l'utilisation de `aria-disabled={pending}`. Il s'agit d'une pratique d'accessibilité importante, garantissant que les lecteurs d'écran annoncent correctement l'état désactivé.
Étape 3 : Assemblage du formulaire principal avec `useFormState`
Maintenant, rassemblons tout dans notre composant formulaire principal. Nous utiliserons `useFormState` pour connecter notre interface utilisateur à l'action `registerUser`.
Fichier : components/RegistrationForm.js
'use client';
import { experimental_useFormState as useFormState } from 'react-dom';
import { registerUser, initialState } from '../actions/authActions';
import { SubmitButton } from './SubmitButton';
export function RegistrationForm() {
const [state, formAction] = useFormState(registerUser, initialState);
return (
<form action={formAction}>
<h2>S'inscrire</h2>
{state?.message && !state.errors && <p style={{ color: 'green' }}>{state.message}</p>}
{state?.message && state.errors && <p style={{ color: 'red' }}>{state.message}</p>}
<div>
<label htmlFor="username">Nom d'utilisateur</label>
<input id="username" name="username" type="text" />
{state?.errors?.username && (
<p style={{ color: 'red' }} aria-live="polite">
{state.errors.username[0]}
</p>
)}
</div>
<div>
<label htmlFor="email">E-mail</label>
<input id="email" name="email" type="email" />
{state?.errors?.email && (
<p style={{ color: 'red' }} aria-live="polite">
{state.errors.email[0]}
</p>
)}
</div>
<div>
<label htmlFor="password">Mot de passe</label>
<input id="password" name="password" type="password" />
{state?.errors?.password && (
<p style={{ color: 'red' }} aria-live="polite">
{state.errors.password[0]}
</p>
)}
</div>
<SubmitButton label="Créer un compte" />
</form>
);
}
Ce composant est maintenant déclaratif et propre. Il ne gère aucun état lui-même, à part l'objet `state` fourni par `useFormState`. Son seul travail est de rendre l'interface utilisateur en fonction de cet état. La logique de désactivation du bouton est encapsulée dans `SubmitButton`, et toute la logique de validation se trouve dans `authActions.js`. Cette séparation des préoccupations est une énorme victoire pour la maintenabilité.
Techniques avancées et meilleures pratiques professionnelles
Bien que le modèle de base soit puissant, les applications du monde réel nécessitent souvent plus de nuances. Explorons quelques techniques avancées.
L'approche hybride : Fusionner la validation instantanée et la validation post-soumission
La validation basée sur l'état est excellente pour les vérifications côté serveur, mais attendre un aller-retour réseau pour dire à un utilisateur que son e-mail n'est pas valide peut être lent. Une approche hybride est souvent la meilleure :
- Utiliser la validation HTML5 : N'oubliez pas les bases ! Les attributs tels que `required`, `type="email"`, `minLength` et `pattern` fournissent un feedback instantané et natif au navigateur sans frais.
- Validation légère côté client : Pour les vérifications purement cosmétiques ou de formatage (par exemple, l'indicateur de force du mot de passe), vous pouvez toujours utiliser une quantité minimale de gestionnaires `useState` et `onChange`.
- Autorité côté serveur : Réservez l'action serveur pour la validation la plus critique, de la logique métier qui ne peut pas être effectuée côté client (par exemple, la vérification des noms d'utilisateur uniques, la validation par rapport aux enregistrements de la base de données).
Cela vous donne le meilleur des deux mondes : un feedback immédiat pour les erreurs simples et une validation faisant autorité pour les règles complexes.
Accessibilité (A11y) : Création de formulaires pour tous
L'accessibilité est non négociable. Lors de l'implémentation de la validation basée sur l'état, gardez ces points à l'esprit :
- Annoncer les erreurs : Dans notre exemple, nous avons utilisé `aria-live="polite"` sur les conteneurs de messages d'erreur. Cela indique aux lecteurs d'écran d'annoncer le message d'erreur dès qu'il apparaît, sans interrompre le flux actuel de l'utilisateur.
- Associer les erreurs aux entrées : Pour une connexion plus robuste, utilisez l'attribut `aria-describedby`. L'entrée peut pointer vers l'ID de son conteneur de messages d'erreur, créant ainsi un lien programmatique.
- Gestion de la focalisation : Après une soumission avec des erreurs, envisagez de déplacer par programme la focalisation vers le premier champ non valide. Cela évite aux utilisateurs d'avoir à chercher ce qui n'a pas fonctionné.
Interface utilisateur optimiste avec la propriété `data` de `useFormStatus`
Imaginez une application de médias sociaux où un utilisateur publie un commentaire. Au lieu d'afficher un spinner pendant une seconde, vous pouvez donner l'impression que l'application est instantanée. La propriété `data` de `useFormStatus` est parfaite pour cela.
Lorsque le formulaire est soumis, `pending` devient true et `data` est rempli avec le `FormData` de la soumission. Vous pouvez immédiatement rendre le nouveau commentaire dans un état visuel temporaire, "en attente", en utilisant ces `data`. Si l'action serveur réussit, vous remplacez le commentaire en attente par les données finales du serveur. Si elle échoue, vous pouvez supprimer le commentaire en attente et afficher une erreur. Cela rend l'application incroyablement réactive.
Naviguer dans les eaux "expérimentales"
Il est essentiel de traiter le préfixe "experimental" dans `experimental_useFormStatus` et `experimental_useFormState`.
Ce que "expérimental" signifie vraiment
Lorsque React qualifie une API d'expérimentale, cela signifie :
- L'API peut changer : Le nom, les arguments ou les valeurs de retour pourraient être modifiés dans une future version de React sans suivre la version sémantique standard (SemVer) pour les changements cassants.
- Il pourrait y avoir des bugs : En tant que nouvelle fonctionnalité, elle peut avoir des cas limites qui ne sont pas encore entièrement compris ou résolus.
- La documentation peut être clairsemée : Bien que les concepts de base soient documentés, des guides détaillés sur les modèles avancés peuvent encore être en évolution.
Quand adopter et quand attendre
Alors, devriez-vous l'utiliser dans votre projet ? La réponse dépend de votre contexte :
- Bon pour : Projets personnels, outils internes, startups ou équipes à l'aise avec la gestion des changements d'API potentiels. L'utiliser dans un framework comme Next.js (qui a intégré ces fonctionnalités dans son App Router) est généralement un pari plus sûr, car le framework peut aider à abstraire une partie du changement.
- Utiliser avec prudence pour : Applications d'entreprise à grande échelle, systèmes critiques ou projets avec des contrats de maintenance à long terme où la stabilité de l'API est primordiale. Dans ces cas, il peut être prudent d'attendre que les hooks soient promus à une API stable.
Gardez toujours un œil sur le blog et la documentation officiels de React pour les annonces concernant la stabilisation de ces hooks.
Conclusion : L'avenir des formulaires dans React
L'introduction de `experimental_useFormStatus` et de ses API associées est plus qu'un simple nouvel outil ; elle représente un changement philosophique dans la façon dont nous construisons des expériences interactives avec React. En adoptant les fondations de la plateforme web et en co-localisant la logique avec état sur le serveur, nous pouvons construire des applications plus simples, plus résilientes et souvent plus performantes.
Nous avons vu comment `useFormStatus` fournit un moyen propre et découplé pour les composants de réagir au cycle de vie d'une soumission de formulaire. Il élimine le prop drilling pour les états en attente et permet des composants d'interface utilisateur élégants et autonomes comme un `SubmitButton` intelligent. Lorsqu'il est combiné avec `useFormState`, il déverrouille le modèle puissant de validation basée sur l'état, où le serveur est l'autorité ultime et la principale responsabilité du client est de rendre l'état renvoyé par l'action serveur.
Bien que la balise "experimental" justifie un certain degré de prudence, la direction est claire. L'avenir des formulaires dans React est celui de l'amélioration progressive, de la gestion d'état simplifiée et d'une intégration puissante et transparente entre la logique client et serveur. En maîtrisant ces nouveaux hooks aujourd'hui, vous n'apprenez pas seulement une nouvelle API ; vous vous préparez à la prochaine génération de développement d'applications web avec React.